<?php
/**
 * Created by dh2y.
 * Blog: http://blog.csdn.net/sinat_22878395
 * Date: 2018/4/26 0026 16:26
 * for: 短信SDK
 */

namespace sms\template;

/** .-----------------------------使用说明---------------------------------
$sms = new Sms();
$sms->sendSms('17xx11076xx','你好，dh2y先生。我发给短信测试一下啦');//普通短信


$sms->sendSmsCode('17xx11076xx','register');                 //验证码短信
$sms->verifySmsCode('17xx11076xx',587620);                   //验证短信的验证码
echo $sms->getError();             //获取失败错误信息(验证，发送等错误信息)
-------------------------------------------------------------------*/

class Sms
{
    /**
     * 操作句柄
     * @var string
     * @access protected
     */
    protected $handler    ;

    protected $config = [];

    protected $scene;      //验证码使用场景

    protected $message;

    protected $model;

    protected $length = 6;

    protected $test = false;

    private static $instance=null;  //创建静态单列对象变量

    /**
     * SmsTemplate constructor.
     * @param array $config
     * @throws \Exception
     */
    private function __construct($config =[]){
        if($C = config('sms')){
            $config = array_merge($C['smsSdk'],$config);
            $this->scene = $C['smsScene'];
            $this->test = $C['isTest'];
            $this->model = $C['smsModel'];
            $this->length = $C['length'];
        }
        /* 获取配置 */
        $this->config   =   array_merge($this->config, $config);
        $class = '\\sms\\template\\service\\'. ucfirst($this->config['class']);
        if(class_exists($class)){
            $this->handler  =   new $class($this->config);
        }else{
            // 类没有定义
            throw new \Exception('没有这个短信服务商');
        }
    }

    /**
     * 单列模式
     * @param array $config
     * @return Sms|null
     */
    public static function getInstance($config = array()){
        if(empty(self::$instance)) {
            self::$instance=new Sms($config);
        }
        return self::$instance;
    }

    /**
     * 克隆函数私有化，防止外部克隆对象
     * @throws \Exception
     */
    private function __clone(){
        throw new \Exception('禁止克隆');
    }

    /**
     * 返还错误信息
     * @return string
     */
    public function getError(){
        return $this->handler->getError();
    }

    /**
     * 设置错误信息
     * @param $error
     */
    public function setError($error){
        $this->handler->setError($error);
    }

    /**
     * 生成验证码
     * @return int|string
     */
    protected function getCode(){
        $start = '';
        $end = '';
        for($i=0;$i<$this->length;$i++){
            $start .='1';
            $end .='9';
        }

        //如果是测试环境$this->length长度1
        return $this->test?$start:rand($start,$end);
    }

    /**
     * 发送验证码
     * @param string $phone 手机号
     * @param string $scene 短信验证码场景
     * @param \Closure $function
     * @param string $code
     * @return bool
     */
    public function sendSmsCode($phone,$scene,\Closure $function=null,$code=''){
        if(!isset($code)||$code==''){
            $code = $this->getCode();
        }

        //发送内容
        $message = str_replace(['%code%'], [$code],$this->scene[$scene]['message']);
        $message = '【'.$this->config['signature'].'】'.$message;
        $this->message = $message;

        //不是测试环境真发
        if(!$this->test){
            $send = $this->handler->sendSms($phone, $this->scene[$scene]['code'],["code" =>$code]);
        }

        if($this->test||$send){
            $modelId = $this->storeCode($phone,$code,$scene);
            if($function!=null){
                $function($phone,$code,$modelId);
            }
            return true;
        }else{
            $this->setError($this->handler->getError());
            return false;
        }
    }

    /**
     * 短信发送待验证信息存储
     * @param $phone
     * @param $code
     * @param $scene
     * @return int
     */
    protected function storeCode($phone,$code,$scene){
        $sms = [
            'sms_phone' =>  $phone,
            'sms_code'  =>  $code,
            'sms_expire'=>  time()+300,
            'sms_time'  =>  time()+60
        ];
        $sms_store = json_encode($sms);

        //是否是模型存储
        if ($this->model){
            $r = new \ReflectionClass($this->model);
            $model = $r->newInstance();
            $model->save([
                'phone' => $phone,
                'scene' => $scene,
                'message' => $this->message,
                'sms_store' => $sms_store
            ]);
            return $model->getLastInsID();
        }else{

            session('sms_store',$sms_store);
            return 0;
        }
    }

    /**
     * 获取存储的待验证信息
     * @param $phone
     * @return mixed
     */
    protected function getStoreCode($phone){
        if ($this->model){
            $r = new \ReflectionClass($this->model);
            $model = $r->newInstance();
            $sms_store = $model->where('phone',$phone)->order('id','desc')->value('sms_store');
        }else{
            $sms_store = session('sms_store');
        }
        return json_decode($sms_store);
    }

    /**
     * 获取发送的短信消息内容
     * @return mixed
     */
    public function getMessage(){
        return $this->message;
    }

    /**
     * 验证短信验证码
     * @param $phone
     * @param $code
     * @param bool $clean 验证完清理session
     * @return bool  false验证失败  true验证成功   失败原因通过getError()获取
     */
    public function verifySmsCode($phone,$code,$clean = true){
        if(!$code || !$phone){
            $this->setError('验证码或手机不存在！');
            return false;
        }

        $sms = $this->getStoreCode($phone);
        if($sms->sms_expire<time()){
            $this->setError('验证码已失效,请重新发送');
            return false;
        }

        if($sms->sms_phone!=$phone){
            $this->setError('短信验证码手机错误！');
            return false;
        }

        //验证码通过
        if($sms->sms_phone==$phone&&$sms->sms_code== $code){
            if($clean){
                $this->cleanStoreCode($phone);
            }
            return true;
        }

        $this->setError('验证码错误！');
        return false;
    }

    /**
     * 清除存储的待验证信息
     * @param $phone
     */
    protected function cleanStoreCode($phone){
        $sms_store = ['sms_phone'=>null,'sms_code'=>null,'sms_expire'=>null];
        $sms_store = json_encode($sms_store);
        if ($this->model){
            $r = new \ReflectionClass($this->model);
            $model = $r->newInstance();
            $sms = $model->where('phone',$phone)->order('id','desc')->find();
            $sms->sms_store = $sms_store;
            $sms->save();
        }else{
            session('sms_store',json_encode($sms_store));
        }
    }

    /**
     * 发送短信
     * @param $phone
     * @param $scene
     * @param array $param ["code" => "12345","product" => "ddd"]
     * @return bool
     */
    public function sendSms($phone, $scene,$param = []){
        //发送内容
        if(!empty($param) && is_array($param)){
            $one = [];$two = [];
            foreach ($param as $key=>$value){
                $one[] = '%'.$key.'%';
                $two[] = $value;
            }
            $message = str_replace($one, $two,$this->scene[$scene]['message']);
            $message = '【'.$this->config['signature'].'】'.$message;
            $this->message = $message;
        }else{
            $this->message = $this->scene[$scene]['message'];
        }

        //是否是测试发送
        if($this->test){
            return true;
        }

        if($this->handler->sendSms($phone, $this->scene[$scene]['code'],$param)){
            return true;
        }else{
            $this->setError($this->handler->getError());
            return false;
        }
    }

    public function __call($method,$args){
        //调用缓存类型自己的方法
        if(method_exists($this->handler, $method)){
            return call_user_func_array(array($this->handler,$method), $args);
        }else{
            // 类没有定义
            throw new \Exception('没有这个短信服务商驱动方法:'.$method);
        }
    }
}